/**
 * Socket Device Filesystem Driver
 *
 * @file socketfile.c
 * @author <a href="mailto:c_graham@hinge.mistral.co.uk>Craig Graham</a>
 * @ingroup fsdevices
 * @{
 */

#include <config.h>
#include <file.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <file.h>
#include <debug.h>
#include <adl.h>
#include <ls588.h>
#include <semaphore.h>
#include <interupt.h>
#include <sys/socket.h>
#include <malloc.h>
//#include "netconf.h"
//#include "net.h"
#include "netdebug.h"
#include "netbuf.h"
#include "netppp.h"
#include "nettcp.h"
#include "netudp.h"

static int socket_chdir(const char *dir);
static int socket_mkdir(const char *dir);
static void *socket_open(const char *name,int mode);
static int socket_close(void *sdata);
static long socket_read(void *sdata, unsigned char *buf, long l);
static long socket_write(void *sdata, const unsigned char *buf, long l);
static long socket_lseek(void *sdata, long offset, int whence);
static int socket_fdelete(const char *name);
static long socket_ioctl(void *sdata,UI32 ioctlValue,void *data);
static long socket_poll(void *sdata);

/**
 * Internal socket information
 */
typedef struct {
	int domain;
	int type;
	int protocol;
	int id;
} SOCKET_FS_DATA;

/**
 * Exported driver interface for Socket Filesystem
 */
FS_DRIVER socketfs={
	/** Device Name */
	"SocketFS",
	/** chdir */
	socket_chdir,
	/** mkdir */
	socket_mkdir,
	/** open */
	socket_open,
	/** close */
	socket_close,
	/** read */
	socket_read,
	/** write */
	socket_write,
	/** lseek */
	socket_lseek,
	/** fdelete */
	socket_fdelete,
	/** ioctl */
	socket_ioctl,
	/** poll */
	socket_poll
};

/**
 * stub for chdir
 */
static int socket_chdir(const char *dir)
{
	return 0;
}

/**
 * stub for mkdir
 */
static int socket_mkdir(const char *dir)
{
	return 0;
}

/**
 * Open the socket device
 */
static void *socket_open(const char *name,int mode)
{
	SOCKET_FS_DATA *sdata;

	//GENERAL_DEBUG(("socket_open:'%s'\n",name));

	sdata=(SOCKET_FS_DATA*)malloc(sizeof(SOCKET_FS_DATA));
	if(sdata==NULL)
		return NULL;
	sdata->id=-1;
	sdata->domain=AF_UNSPEC;
	sdata->type=SOCK_STREAM;
	sdata->protocol=0;

	return (void*)sdata;
}

/**
 * Close the socket device
 */
static int socket_close(void *sdata)
{
	//GENERAL_DEBUG(("socket_close\n"));

	switch(((SOCKET_FS_DATA*)sdata)->protocol)
	{
		case IPPROTO_TCP:
			tcpClose(((SOCKET_FS_DATA*)sdata)->id);
			//tcpWait(((SOCKET_FS_DATA*)sdata)->id);
			break;
		case IPPROTO_UDP:
			udpClose(((SOCKET_FS_DATA*)sdata)->id);
			break;
	}

	return 0;
}

/**
 * Read data from the socket device
 */
static long socket_read(void *sdata, unsigned char *buf, long l)
{
	int rtn;
	//GENERAL_DEBUG(("socket_read: len=%ld\n",l));

//	printf("socket_read: l=%ld\n",l);

	switch(((SOCKET_FS_DATA*)sdata)->protocol)
	{
		case IPPROTO_TCP:
			rtn=tcpRead(((SOCKET_FS_DATA*)sdata)->id,buf,l);
			break;
		case IPPROTO_UDP:
			rtn=udpRead(((SOCKET_FS_DATA*)sdata)->id,buf,l);
			break;
	}
	return rtn;
}

/**
 * Write data to the socket device
 */
static long socket_write(void *sdata, const unsigned char *buf, long l)
{
	int rtn;

//	printf("socket_write: l=%ld\n",l);

	switch(((SOCKET_FS_DATA*)sdata)->protocol)
	{
		case IPPROTO_TCP:
			rtn=tcpWrite(((SOCKET_FS_DATA*)sdata)->id,buf,l);
			break;
		case IPPROTO_UDP:
			rtn=udpWrite(((SOCKET_FS_DATA*)sdata)->id,buf,l);
			break;
		default:
			printf("socket_write: unknown protocol\n");
			break;
	}
	return rtn;
}

/**
 * stub for lseek
 */
static long socket_lseek(void *sdata, long offset, int whence)
{
	return 0;
}

/**
 * stub for fdelete
 */
static int socket_fdelete(const char *name)
{
	return 0;
}

/**
 * stub for ioctl
 */
static long socket_ioctl(void *sdata,UI32 ioctlValue,void *data)
{
	long rtn;

	switch(ioctlValue)
	{
		case IOCTL_DEVCAPS:
			rtn=IOCTL_DEVCAPS|IOCTL_PENDINGDATALENGTH|IOCTL_SOCKDOMAIN|IOCTL_SOCKTYPE|IOCTL_SOCKPROTOCOL;
			break;
		case IOCTL_PENDINGDATALENGTH:
			//GENERAL_DEBUG(("should return amount of data available here\n"));
			rtn=0;
			break;
		case IOCTL_SOCKDOMAIN:
			if(data)
				((SOCKET_FS_DATA*)sdata)->domain=*(int*)data;
			rtn=((SOCKET_FS_DATA*)sdata)->domain;
			break;
		case IOCTL_SOCKTYPE:
			if(data)
				((SOCKET_FS_DATA*)sdata)->type=*(int*)data;
			rtn=((SOCKET_FS_DATA*)sdata)->type;
			break;
		case IOCTL_SOCKPROTOCOL:
			if((data)&&(((SOCKET_FS_DATA*)sdata)->id==-1))
			{
				((SOCKET_FS_DATA*)sdata)->protocol=*(int*)data;
				switch(((SOCKET_FS_DATA*)sdata)->protocol)
				{
					case IPPROTO_UDP:
						((SOCKET_FS_DATA*)sdata)->id=udpOpen();
						printf("socket_ioctl: protocol=udp, ucIP ID=%d\n",((SOCKET_FS_DATA*)sdata)->id);
						break;

					case IPPROTO_TCP:
						((SOCKET_FS_DATA*)sdata)->id=tcpOpen();
						printf("socket_ioctl: protocol=tcp, ucIP ID=%d\n",((SOCKET_FS_DATA*)sdata)->id);
//						{
//							int inCnt=10;
//							tcpIOCtl(((SOCKET_FS_DATA*)sdata)->id, TCPCTLS_KEEPALIVE, &inCnt);
//						}
						break;
					default:
						//GENERAL_DEBUG(("socket_ioctl:IOCTL_SOCKPROTOCOL: unsupported protocol %d\n",((SOCKET_FS_DATA*)sdata)->protocol));
						((SOCKET_FS_DATA*)sdata)->protocol=-1;
						break;
				}
			}
			rtn=((SOCKET_FS_DATA*)sdata)->protocol;
			break;
		default:
			rtn=-1;
			break;
	}
	return rtn;
}

static long socket_poll(void *sdata)
{
	long rtn;
	switch(((SOCKET_FS_DATA*)sdata)->protocol)
	{
		case IPPROTO_UDP:
			rtn=udpPoll(((SOCKET_FS_DATA*)sdata)->id);
			break;
		case IPPROTO_TCP:
			rtn=tcpPoll(((SOCKET_FS_DATA*)sdata)->id);
			break;
	}
	return rtn;
}

/**
 * Helper function - standard Unix way of creating a socket
 */
int socket(int domain, int type, int protocol)
{
	int fd;

	fd=open("/dev/socket/0",O_RDWR);
	if(fd>=0)
	{
		printf("socket: socket fd=%d\n",fd);
		ioctl(fd,IOCTL_SOCKDOMAIN,&domain);
		ioctl(fd,IOCTL_SOCKTYPE,&type);
		if(protocol==0)
			protocol=(type==SOCK_STREAM)?IPPROTO_TCP:IPPROTO_UDP;
		ioctl(fd,IOCTL_SOCKPROTOCOL,&protocol);
	}else{
		while(1);
			printf("socket: cann't open /dev/socket/0\n");
	}
	return fd;
}

/**
 * Helper function - standard Unix way of connecting a socket to a server.
 */
int  connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen)
{
	FILE *sf;
	int rtn;

	sf=fdopen(sockfd, "rw");
	printf("connect:socket fd=%d\n",sockfd);
	if(sf==NULL)
	{
		printf("connect: invalid socket handle\n");
		return -1;
	}
	switch(serv_addr->na_family)
	{
		case AF_INET:
			switch(((SOCKET_FS_DATA*)sf->driverData)->protocol)
			{
				case IPPROTO_TCP:
					rtn=tcpConnect(((SOCKET_FS_DATA*)sf->driverData)->id, (const struct sockaddr_in*)serv_addr, 0);
					printf("tcpConnect returned %d\n",rtn);
					break;
				case IPPROTO_UDP:
					rtn=udpConnect(((SOCKET_FS_DATA*)sf->driverData)->id, (const struct sockaddr_in*)serv_addr, 0);
					printf("udpConnect returned %d\n",rtn);
					break;
				default:
					printf("connect: unsupported protocol %ld\n",((SOCKET_FS_DATA*)sf->driverData)->protocol);
					break;
			}
			break;
		case AF_UNSPEC:
			switch(((SOCKET_FS_DATA*)sf->driverData)->protocol)
			{
				case IPPROTO_TCP:
					rtn=tcpDisconnect(((SOCKET_FS_DATA*)sf->driverData)->id);
					break;
				case IPPROTO_UDP:
					//rtn=udpDisconnect(((SOCKET_FS_DATA*)sf->driverData)->id);
					break;
				default:
					printf("connect: unsupported protocol %ld\n",((SOCKET_FS_DATA*)sf->driverData)->protocol);
					break;
			}
			break;
		default:
			printf("connect: unsupported address family %ld\n",serv_addr->na_family);
			rtn=-1;
			break;
	}
	return rtn;
}

int accept(int sockfd, struct sockaddr  *addr, socklen_t *addrlen)
{
	FILE *sf;
	int remoteId,remoteFD;

	sf=fdopen(sockfd, "rw");
	if(sf==NULL)
	{
		printf("accept: invalid socket handle\n");
		return -1;
	}
	switch(((SOCKET_FS_DATA*)sf->driverData)->protocol)
	{
		case IPPROTO_TCP:
			remoteId=tcpAccept(((SOCKET_FS_DATA*)sf->driverData)->id, (struct sockaddr_in *)addr);
			if(remoteId>=0)
			{
				FILE *r_sf;
				remoteFD=open("/dev/socket/0",O_RDWR);
				if(remoteFD>=0)
				{
					r_sf=fdopen(remoteFD,"rw");

					printf("accept: remoteFD=%d\n",remoteFD);
					((SOCKET_FS_DATA*)r_sf->driverData)->protocol=IPPROTO_TCP;
					((SOCKET_FS_DATA*)r_sf->driverData)->id=remoteId;
					return remoteFD;
				}else{
					printf("accept: couldn't open /dev/socket/0\n");
					tcpClose(remoteId);
				}
			}else{
				printf("accept: uC/IP returned error code %d\n",remoteId);
			}
			break;
		default:
			printf("accept: cann't accept() on this class of socket\n");
			break;
	}
	return -1;
}

int listen(int sockfd, int backlog)
{
	FILE *sf;
	int rtn=0;

	sf=fdopen(sockfd, "rw");
	if(sf==NULL)
	{
		printf("listen: cann't get FILE for fd=%d\n",sockfd);
		return -1;
	}
	switch(((SOCKET_FS_DATA*)sf->driverData)->protocol)
	{
		case IPPROTO_TCP:
			printf("calling tcpListen(%d,%d)\n",((SOCKET_FS_DATA*)sf->driverData)->id,backlog);
			rtn=tcpListen(((SOCKET_FS_DATA*)sf->driverData)->id, backlog);
			if(rtn<0)
			{
				setErrno(rtn);
				rtn=-1;
			}else{
				rtn=0;
			}
			break;
		case IPPROTO_UDP:
			printf("calling udpListen(%d,%d)\n",((SOCKET_FS_DATA*)sf->driverData)->id,backlog);
			rtn=udpListen(((SOCKET_FS_DATA*)sf->driverData)->id, backlog);
			break;
		default:
			printf("listen: unknown protocol\n");
			rtn=-1;
			break;
	}
	return rtn;
}

int bind(int sockfd, struct sockaddr *addr, socklen_t addrlen)
{
	FILE *sf;
	int rtn;

	//GENERAL_DEBUG(("bind: sockfd=%d\n",sockfd));
	printf("bind: socket fd=%d\n",sockfd);

	sf=fdopen(sockfd, "rw");
	if(sf==NULL)
	{
		printf("bind: cann't get FILE for fd=%d\n",sockfd);
		return -1;
	}
	switch(((SOCKET_FS_DATA*)sf->driverData)->protocol)
	{
		case IPPROTO_TCP:
			//GENERAL_DEBUG(("calling tcpBind()\n"));
			rtn=tcpBind(((SOCKET_FS_DATA*)sf->driverData)->id, (struct sockaddr_in*)addr);
			printf(" - using tcp\n");
			break;
		case IPPROTO_UDP:
			//GENERAL_DEBUG(("calling udpBind()\n"));
			rtn=udpBind(((SOCKET_FS_DATA*)sf->driverData)->id, (struct sockaddr_in*)addr);
			printf(" - using udp\n");
			break;
		default:
			printf("bind: unknown protocol\n");
			rtn=-1;
			break;
	}

	return rtn;
}

long recvfrom(int sockfd,  void  *buf,  size_t len, int flags,  struct sockaddr *from, socklen_t *fromlen)
{
	FILE *sf;
	int rtn;

	//GENERAL_DEBUG(("recvfrom()\n"));

	sf=fdopen(sockfd, "rw");
	if(sf==NULL)
		return -1;
	switch(((SOCKET_FS_DATA*)sf->driverData)->protocol)
	{
		case IPPROTO_TCP:
			rtn=tcpRead(((SOCKET_FS_DATA*)sf->driverData)->id,buf,len);
			break;
		case IPPROTO_UDP:
			//GENERAL_DEBUG(("calling udpRecvFrom()\n"));
			rtn=udpRecvFrom(((SOCKET_FS_DATA*)sf->driverData)->id, buf, len, (struct sockaddr_in*)from);
			break;
	}
	if(fromlen)
		*fromlen=sizeof(struct sockaddr_in);
	return rtn;
}

long sendto(int sockfd,  const void  *buf,  size_t len, int flags,  const struct sockaddr *to, socklen_t tolen)
{
	FILE *sf;
	int rtn;

	//GENERAL_DEBUG(("sendto()\n"));

	sf=fdopen(sockfd, "rw");
	if(sf==NULL)
		return -1;
	switch(((SOCKET_FS_DATA*)sf->driverData)->protocol)
	{
		case IPPROTO_TCP:
			tcpDisconnect(((SOCKET_FS_DATA*)sf->driverData)->id);
			rtn=tcpConnect(((SOCKET_FS_DATA*)sf->driverData)->id, (const struct sockaddr_in*)to, 0);
			if(rtn<0)
				break;
			rtn=tcpWrite(((SOCKET_FS_DATA*)sf->driverData)->id,buf,len);
			break;
		case IPPROTO_UDP:
			//GENERAL_DEBUG(("calling udpSendTo()\n"));
			rtn=udpSendTo(((SOCKET_FS_DATA*)sf->driverData)->id, buf, len, (const struct sockaddr_in*)to);
			break;
	}
	return rtn;
}

void unblock(int sockfd)
{
	FILE *sf;
	sf=fdopen(sockfd, "rw");
	if(sf==NULL)
		return;
	switch(((SOCKET_FS_DATA*)sf->driverData)->protocol)
	{
		case IPPROTO_TCP:
			break;
		case IPPROTO_UDP:
			//GENERAL_DEBUG(("calling udpUnblockRead()\n"));
			udpUnblockRead(((SOCKET_FS_DATA*)sf->driverData)->id);
			break;
	}
}

int setsockopt(int s, int level, int optname,  const  void *optval, socklen_t optlen)
{
	// stub for setsockopt
}

int send(int s, const void *msg, size_t len, int flags)
{
	FILE *sf;
	sf=fdopen(s, "rw");
	if(sf==NULL)
	{
		printf("send: error getting FILE from descriptor %d\n",s);
		return -1;
	}
	return socket_write(sf->driverData, (const unsigned char*)msg, len);
}

int recv(int s, void *buf, size_t len, int flags)
{
	FILE *sf;
	sf=fdopen(s, "rw");
	if(sf==NULL)
	{
		printf("recv: error getting FILE from descriptor %d\n",s);
		return -1;
	}
	socket_read(sf->driverData, (unsigned char*)buf, len);
}

/** @} */
